import { ref, toRefs, UnwrapRef } from 'vue'

/**
 * data 数据
 * error 错误信息
 * stat 状态 [空闲，进行中，成功，失败]
 */
interface State<D> {
  stat: 'idle' | 'loading' | 'success' | 'error'
  data: D | null
  error: Error | null
}

// 默认初始状态
const defaultInitialState: State<null> = {
  stat: 'idle',
  data: null,
  error: null
}

// 默认配置
const defaultConfig = {
  // 是否需要抛出异常
  throwOnError: true
}

/**
 * 用 useAsync 来管理异步请求
 * @param initialState
 * @param initialConfig
 */
export const useAsync = <D>(initialState?: State<D>, initialConfig?: typeof defaultConfig) => {
  // 用传入的状态覆盖默认状态
  const state = ref<State<D>>({
    ...defaultInitialState,
    ...initialState
  })
  // 用传入的配置覆盖默认配置
  const config = { ...defaultConfig, ...initialConfig }

  /**
   * 返回成功
   * @param data
   */
  const setData = (data: UnwrapRef<D>) => {
    state.value.stat = 'success'
    state.value.data = data
    state.value.error = null
  }
  /**
   * 返回失败
   * @param error
   */
  const setError = (error: Error) => {
    state.value.stat = 'error'
    state.value.data = null
    state.value.error = error
  }

  /**
   * 重复调用函数
   */
  const retry = ref<Nullable<Fn>>(null)

  /**
   * 运行 promise
   * @param promise
   * @param runConfig
   */
  const run = (
    promise: Promise<IApiResponseData<UnwrapRef<D>>>,
    runConfig?: { retry: () => Promise<IApiResponseData<UnwrapRef<D>>> }
  ): Promise<UnwrapRef<D>> => {
    if (!promise || !promise.then) {
      throw new Error('请传入 promise 数据')
    }

    // 设置重复调用
    if (runConfig?.retry) {
      retry.value = () => run(runConfig.retry(), runConfig)
    }

    // 开启 loading
    state.value.stat = 'loading'

    return promise
      .then(res => {
        const { data } = res
        setData(data)
        return data
      })
      .catch(err => {
        // 这里 catch 是会消化掉 error
        // 如果不主动抛出，外部函数接收不到错误信息的
        // 若需要在外部函数中接收错误信息，需要 return Promise.reject(err)
        setError(err)
        if (config.throwOnError) {
          return Promise.reject(err)
        }
        return err
      })
  }

  // 暴露状态和方法
  return {
    isIdle: state.value.stat === 'idle',
    isLoading: state.value.stat === 'loading',
    isError: state.value.stat === 'error',
    isSuccess: state.value.stat === 'success',
    run,
    setData,
    setError,
    retry,
    ...toRefs(state.value)
  }
}
